/*
 * @Author: soso
 * @Date: 2022-01-29 10:25:06
 * @LastEditTime: 2022-01-29 18:41:42
 * @LastEditors: Please set LastEditors
 * @Description: 目录的树形结构存储
 * @FilePath: /go-mesh-sync/sdirs/node.go
 */
package sodirs

import (
	"bytes"
	"crypto/md5"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	gobFile = "dirs.gob"
)

type DNode struct {
	parent  *DNode
	Name    string      `yaml:"name"`
	Size    int64       `yaml:"size"`
	Mode    fs.FileMode `yaml:"mode"`
	ModTime time.Time   `yaml:"modTime"`
	IsDir   bool        `yaml:"isDir"`

	Level    uint8             `yaml:"level"`
	Md5      string            `yaml:"md5"`
	Children map[string]*DNode `yaml:"children"`
}

func NewRootNode(path string) (*DNode, error) {
	fi, err := os.Stat(path)
	if err != nil {
		return nil, err
	}
	if !fi.IsDir() {
		return nil, errors.New("root node must be a directory")
	}
	return &DNode{Name: path, IsDir: true, Level: 0, Size: 0, Children: map[string]*DNode{}}, nil
}

// 加载目录结构
func (n *DNode) LoadDirStruct() {
	rd, err := ioutil.ReadDir(n.GetPath())

	if err != nil {
		return
	}
	if err == nil {
		for _, fi := range rd {
			newPath := filepath.Join(n.Name, fi.Name())

			node := &DNode{
				Name:    fi.Name(),
				IsDir:   fi.IsDir(),
				Mode:    fi.Mode(),
				ModTime: fi.ModTime(),
				Size:    fi.Size(),

				parent: n,
				Level:  uint8(strings.Count(newPath, "/")),

				Children: map[string]*DNode{},
			}

			// if !node.IsDir {
			// 	node.Md5, _ = HashFileMd5(newPath)
			// }

			n.Children[node.Name] = node

			if node.IsDir {
				node.LoadDirStruct()
			}
		}
	}
}

// 节点路径
func (n *DNode) GetPath() string {
	nn := n
	path := nn.Name
	for {
		if nn.parent != nil {
			nn = nn.parent
			path = filepath.Join(nn.Name, path)
		} else {
			break
		}
	}
	return path
}

// 返回针对根节点的相对路径
func (n *DNode) GetPathRelWithRoot() string {
	nn := n
	path := nn.Name
	for {
		if nn.parent != nil {
			nn = nn.parent
			path = filepath.Join(nn.Name, path)
		} else {
			break
		}
	}
	path, _ = filepath.Rel(nn.Name, path)
	return path
}

// 序列化
func (n *DNode) Serialize() []byte {
	b := bytes.Buffer{}
	e := gob.NewEncoder(&b)
	e.Encode(n)
	return b.Bytes()
}

// 反序列化
func (n *DNode) Unserialize(buf []byte) {
	reader := bytes.NewReader(buf)
	dcd := gob.NewDecoder(reader)
	dcd.Decode(n)
	n.setParent()
}

//写入磁盘数据
func (n *DNode) Save() {
	ioutil.WriteFile(gobFile, n.Serialize(), os.ModePerm)
}

//加载磁盘数据
func (n *DNode) Load() {
	buf, err := ioutil.ReadFile(gobFile)
	if err != nil {
		return
	}
	n.Unserialize(buf)
}

func (n *DNode) setParent() {
	if n == nil {
		return
	}
	for _, nn := range n.Children {
		nn.parent = n
		if nn.IsDir {
			nn.setParent()
		}
	}
}

func (n *DNode) GetParent() *DNode {
	return n.parent
}

// 为节点循环执行回调函数
func (n *DNode) ListCallBack(cb func(node *DNode)) {
	for _, nn := range n.Children {
		cb(nn)
		if nn.IsDir {
			nn.ListCallBack(cb)
		}
	}
}

// 控制台打印目录结构
func (n *DNode) List() {
	for _, nn := range n.Children {
		fmt.Println(nn)
		if nn.IsDir {
			nn.List()
		}
	}
}

// 控制台打印目录结构
func (n *DNode) ListPath() {
	for _, nn := range n.Children {
		fmt.Println(nn.GetPath())
		if nn.IsDir {
			nn.ListPath()
		}
	}
}

// 检查节点是不是最终节点
func (n *DNode) IsEmpty() bool {
	return len(n.Children) == 0
}

// 计算文件md5值
func HashFileMd5(filename string) (md5str string, err error) {
	pFile, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer pFile.Close()

	md5h := md5.New()
	io.Copy(md5h, pFile)
	return hex.EncodeToString(md5h.Sum(nil)), err
}
